#include <assert.h>
#include <math.h>
#include <stdio.h>

#include "../src/data.c"

static void
fill (float*           x,
      double         (*func) (void),
      unsigned int      n)
{
  for (unsigned int i = 0; i < n; ++i)
    {
      x[i] = func ();
    }
}

static void
fill_i (int*            x,
        int           (*func) (void),
        unsigned int    n)
{
  for (unsigned int i = 0; i < n; ++i)
    {
      x[i] = func ();
    }
}

static void
fill_r (Rating*         x,
        unsigned int    n_workers,
        unsigned int    n_labels,
        unsigned int    n_images,
        unsigned int    n)
{
  for (unsigned int i = 0; i < n; ++i)
    {
      x[i].worker = (lrand48 () >> 16) % n_workers;
      x[i].image = (lrand48 () >> 16) % n_images;
      x[i].rating = (lrand48 () >> 16) % n_labels;
    }
}

static double
zero (void)
{
  return 0.0;
}

static double
one (void)
{
  return 1.0;
}

static int
minus_one (void)
{
  return -1.0;
}

static int
unsigned_int_cmp (int a,
                  int b)
{
  if (a < b) { return -1; }
  else if (a > b) { return 1; }
  else { return 0; }
}

static int
float_cmp (float a,
            float b)
{
  if (a < b) { return -1; }
  else if (a > b) { return 1; }
  else { return 0; }
}

static int
fuzzy_cmp (const float* a,
           const float* b,
           unsigned int  n)
{
  const float epsilon = 1e-5;

  for (unsigned int i = 0; i < n; ++i)
    {
      int rv = fabs (a[i] - b[i]) < epsilon ? 0 : float_cmp (a[i], b[i]);

      if (rv) { return rv; }
    }

  return 0;
}

static int
data_cmp (const Data* a,
          const Data* b)
{
  return  unsigned_int_cmp (a->n_workers, b->n_workers) ||
          unsigned_int_cmp (a->rank, b->rank) ||
          unsigned_int_cmp (a->n_labels, b->n_labels) ||
          unsigned_int_cmp (a->n_images, b->n_images) ||
          unsigned_int_cmp (a->n_ratings, b->n_ratings) ||
          float_cmp (a->priorloga.sq_stddev, b->priorloga.sq_stddev) ||
          float_cmp (a->priorgamma.mean, b->priorgamma.mean) ||
          float_cmp (a->priorgamma.sq_stddev, b->priorgamma.sq_stddev) ||
          float_cmp (a->priorlogbeta.mean, b->priorlogbeta.mean) ||
          float_cmp (a->priorlogbeta.sq_stddev, b->priorlogbeta.sq_stddev) ||
          fuzzy_cmp (&a->priorc.mean, &b->priorc.mean, 1) ||
          float_cmp (a->priorc.sq_stddev, b->priorc.sq_stddev) ||
          float_cmp (a->priord.mean, b->priord.mean) ||
          float_cmp (a->priord.sq_stddev, b->priord.sq_stddev) ||
    /*
           memcmp (a->loga,
                   b->loga,
                   a->n_workers * a->rank * sizeof (*a->loga)) ||
           memcmp (a->gamma, b->gamma, a->rank * sizeof (*a->gamma)) || 
    */
         memcmp (a->logbeta, b->logbeta, a->n_images * sizeof (*a->logbeta)) || 
    /*
         memcmp (a->c, b->c, a->n_labels * a->rank * sizeof (*a->c)) || 
         memcmp (a->d, b->d, a->n_labels * a->rank * sizeof (*a->d)) || 
    */
         unsigned_int_cmp (a->n_clamp, b->n_clamp) ||
         memcmp (a->clamp, b->clamp, a->n_images * sizeof (*a->clamp)) || 
         fuzzy_cmp (a->priorz, b->priorz, a->n_labels) ||
         memcmp (a->ratings, b->ratings, a->n_ratings * sizeof (*a->ratings)) ||
         memcmp (a->image_ratings,
                 b->image_ratings,
                 a->n_images * sizeof (*a->image_ratings)) ||
         memcmp (a->worker_ratings,
                 b->worker_ratings,
                 a->n_workers * sizeof (*a->worker_ratings));
}

static void
test_data_inverse_once (void)
{
  /* read_clamp (read_data (print_data (data), print_clamp (data))) == data */

  Data data;
  Data data_dup;

  data.n_workers = 1 + (lrand48 () >> 16) % 10;
  data.rank = 1 + (lrand48 () >> 16) % 10;
  data.n_labels = 1 + (lrand48 () >> 16) % 10;
  data.n_images = 1 + (lrand48 () >> 16) % 10;
  data.n_ratings = 1 + (lrand48 () >> 16) % 10;
  data.priorloga = parameterized_normal_distribution (1);
  data.priorgamma = normal_distribution (0, 1);
  data.priorlogbeta = normal_distribution (1, 1);
  data.priorc = normal_distribution (1.0 / data.rank, 1);
  data.priord = normal_distribution (1.0, 1);

  data.loga = malloc (data.n_workers * data.rank * sizeof (*data.loga));
  fill (data.loga, one, data.n_workers * data.rank);
  data.gamma = malloc (data.rank * sizeof (*data.gamma));
  fill (data.gamma, zero, data.rank);
  data.logbeta = malloc (data.n_images * sizeof (*data.logbeta));
  fill (data.logbeta, one, data.n_images);
  data.c = malloc (data.n_labels * data.rank * sizeof (*data.c));
  fill (data.c, one, data.n_labels * data.rank);
  data.d = malloc (data.n_labels * data.rank * sizeof (*data.d));
  fill (data.d, one, data.n_labels * data.rank);
  data.pz = malloc (data.n_images * data.n_labels * sizeof (*data.pz));
  fill (data.pz, drand48, data.n_images * data.n_labels);
  data.priorz = malloc (data.n_labels * sizeof (*data.priorz));
  fill (data.priorz, drand48, data.n_labels);
  float sum = data.priorz[0];
  for (unsigned int i = 1; i < data.n_labels; ++i)
    {
      sum += data.priorz[i];
    }
  for (unsigned int i = 0; i < data.n_labels; ++i)
    {
      data.priorz[i] /= sum;
    }
  data.n_clamp = 0;
  data.clamp = malloc (data.n_images * sizeof (*data.priorz));
  fill_i (data.clamp, minus_one, data.n_images);
  for (unsigned int i = 0; i < data.n_images; ++i)
    {
      if (drand48 () < 0.5) 
        { 
          data.clamp[i] = (int) (drand48 () * data.n_labels); 
          ++data.n_clamp;
        }
    }

  data.ratings = malloc (data.n_ratings * sizeof (*data.ratings));
  fill_r (data.ratings,
          data.n_workers,
          data.n_labels,
          data.n_images,
          data.n_ratings);

  data.image_ratings = malloc (data.n_images * sizeof (*data.image_ratings));
  memset (data.image_ratings, 0, data.n_images * sizeof (*data.image_ratings));
  for (unsigned int i = 0; i < data.n_ratings; ++i)
    {
      ++data.image_ratings[data.ratings[i].image];
    }

  data.worker_ratings = malloc (data.n_workers * sizeof (*data.worker_ratings));
  memset (data.worker_ratings, 0, data.n_workers * sizeof (*data.worker_ratings));
  for (unsigned int i = 0; i < data.n_ratings; ++i)
    {
      ++data.worker_ratings[data.ratings[i].worker];
    }

  FILE* fp = tmpfile ();

  print_data (fp, &data);
  print_clamp (fp, &data);
  fflush (fp);
  rewind (fp);
  read_data (fp, &data_dup, data.rank);
  read_clamp (fp, &data_dup);

  assert (data_cmp (&data, &data_dup) == 0);

  fclose (fp);
  data_destruct (&data_dup);
  data_destruct (&data);
}

static void
test_data_inverse (void)
{
  for (unsigned int i = 0; i < 100; ++i)
    {
      test_data_inverse_once ();
    }
}

int
main (void)
{
  srand48 (69);
  test_data_inverse ();

  return 0;
}
